﻿// HSS.Interlink.UI.UploadFile.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.html)
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2012
// ----------------------------------------------------------------------------
// File:       UploadFile.cs
// Author:     HSS\gbanta
// Created:    01/17/2012
// Modified:   01/17/2012
// ----------------------------------------------------------------------------
namespace HSS.Interlink.UI
{
	#region Using Directives
	using System;
	using System.ComponentModel;
	using System.IO;
	using System.Windows;
	using System.Windows.Browser;
	using System.Windows.Media;
	#endregion

	#region UploadFile
	/// <summary>
	/// Default model implementation for a file that can be uploaded.
	/// </summary>
	public class UploadFile : INotifyPropertyChanged
	{
		#region Constants
		private int INFO_COLUMN_WIDTH = 29;
		private Brush GRAY = new SolidColorBrush(Colors.Gray);
		private Brush BROWN = new SolidColorBrush(Colors.Brown);
		#endregion

		#region Events

		#region PropertyChanged
		/// <summary>
		/// Event fired when a property has changed.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;
		/// <summary>
		/// Raises the <see cref="PropertyChanged"/> event.
		/// </summary>
		/// <param name="propertyName">The name of the property that changed.</param>
		protected void OnPropertyChanged(string propertyName)
		{
			var handler = this.PropertyChanged;
			if (null != handler)
				handler(this, new PropertyChangedEventArgs(propertyName));
		}
		#endregion

		#region UploadProgressChanged
		/// <summary>
		/// Event fired when the progress has changed.
		/// </summary>
		public event EventHandler<UploadFileProgressEventArgs> UploadProgressChanged;
		/// <summary>
		/// Raises the <see cref="UploadProgressChanged"/> event.
		/// </summary>
		/// <param name="args">The <see cref="UploadFileProgressEventArgs"/> event arguments.</param>
		protected void OnUploadProgressChanged(UploadFileProgressEventArgs args)
		{
			var handler = this.UploadProgressChanged;
			if (null != handler)
				handler(this, args);
		}
		#endregion

		#region UploadCompleted
		/// <summary>
		/// Event fired when the file has completed the upload process.
		/// </summary>
		public event EventHandler<UploadFileCompletedEventArgs> UploadCompleted;
		/// <summary>
		/// Raises the <see cref="UploadCompleted"/> event.
		/// </summary>
		/// <param name="args">The <see cref="UploadFileCompletedEventArgs"/> event arguments.</param>
		protected void OnUploadCompleted(UploadFileCompletedEventArgs args)
		{
			var handler = this.UploadCompleted;
			if (null != handler)
				handler(this, args);
		}
		#endregion

		#endregion

		#region Fields

		/// <summary>
		/// The stream being uploaded.
		/// </summary>
		private Stream uploadStream;
		/// <summary>
		/// The Type of the upload client responsible for deliverying the file to the remote service.
		/// </summary>
		private string clientType;
		/// <summary>
		/// Any configuration information required for the upload client.
		/// </summary>
		private string clientConfig;
		/// <summary>
		/// The Uri of the remote service to receive the uploaded file.
		/// </summary>
		private string serviceUri;

		private IUploadClient client;

		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		public UploadFile()
		{
			this.Results = string.Empty;
		}
		#endregion

		#region Properties

		/// <summary>
		/// A callback to capture any metadata.
		/// </summary>
		protected internal Func<string, string> GetMetadataCallback
		{
			get;
			set;
		}
		/// <summary>
		/// A callback to show a dialog displaying the file details.
		/// </summary>
		protected internal Action<UploadFile> ViewFileDetailCallback
		{
			get;
			set;
		}

		#region Commands

		/// <summary>
		/// Gets or sets the CancelCommand value.
		/// </summary>
		public InterlinkCommand CancelCommand
		{
			get { return this._cancelCommand; }
			set
			{
				if (this._cancelCommand != value)
				{
					this._cancelCommand = value;
					this.OnPropertyChanged("CancelCommand");
				}
			}
		} InterlinkCommand _cancelCommand;
		/// <summary>
		/// Gets or sets the RemoveCommand value.
		/// </summary>
		public InterlinkCommand RemoveCommand
		{
			get { return this._removeCommand; }
			set
			{
				if (this._removeCommand != value)
				{
					this._removeCommand = value;
					this.OnPropertyChanged("RemoveCommand");
				}
			}
		} InterlinkCommand _removeCommand;
		/// <summary>
		/// Gets or sets the ShowDetailCommand value.
		/// </summary>
		public InterlinkCommand ShowDetailCommand
		{
			get { return this._showDetailCommand; }
			set
			{
				if (this._showDetailCommand != value)
				{
					this._showDetailCommand = value;
					this.OnPropertyChanged("ShowDetailCommand");
				}
			}
		} InterlinkCommand _showDetailCommand;

		#endregion

		#region Details
		/// <summary>
		/// Gets or sets the results from the server for this file.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This property will be set when the upload has completed successfully, overwriting any previous value.
		/// </para>
		/// <para>
		/// If the server does not provide a result, this may not have a discernible value other than an empty string.
		/// </para>
		/// </remarks>
		[Browsable(false)]
		[ScriptableMember]
		public string Results
		{
			get;
			set;
		}
		/// <summary>
		/// Gets or sets the state of the file's upload processing.
		/// </summary>
		/// <remarks>
		/// <para>
		/// See the <see cref="FileState"/> enumerator for details of the available states.
		/// </para>
		/// </remarks>
		[Browsable(false)]
		public FileState State
		{
			get
			{
				return _state;
			}
			protected set
			{
				_state = value;
				this.OnCommandStateChanged();
				this.OnPropertyChanged("State");
				this.OnPropertyChanged("StateString");
				this.UpdateVisualState(value);
			}
		} FileState _state = FileState.Ready;
		/// <summary>
		/// Gets the file's <see cref="State"/> as a <see cref="String"/>.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public string StateString
		{
			get { return this.State.ToString(); }
		}
		/// <summary>
		/// Gets the error string if the upload failed.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public string Error
		{
			get { return _error; }
			protected set
			{
				_error = value;
				this.OnPropertyChanged("Error");
			}
		} string _error = string.Empty;
		/// <summary>
		/// Gets the number of bytes actually uploaded.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public long BytesUploaded
		{
			get { return _bytesUploaded; }
			protected set
			{
				_bytesUploaded = value;
				this.OnPropertyChanged("BytesUploaded");
			}
		} long _bytesUploaded;
		/// <summary>
		/// Gets the percentage complete.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public int Percentage
		{
			get { return _percentage; }
			protected set
			{
				_percentage = value;
				this.OnPropertyChanged("Percentage");
			}
		} int _percentage;
		/// <summary>
		/// Gets the timestamp of when this file began the upload process.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public DateTime StartTimestamp
		{
			get
			{
				if (null == this.client)
					return DateTime.MinValue;
				return this.client.StartTimestamp;
			}
		}
		/// <summary>
		/// Gets the timestamp of when this file finished the upload process.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public DateTime EndTimestamp
		{
			get
			{
				if (null == this.client)
					return DateTime.MinValue;
				return this.client.EndTimestamp;
			}
		}
		/// <summary>
		/// Gets the elapsed time of the upload process.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public TimeSpan ElapsedTime
		{
			get
			{
				if (null == this.client)
					return new TimeSpan();
				return this.client.ElapsedTime;
			}
		}
		/// <summary>
		/// Gets the elapsed time of the upload process in Seconds.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public double ElapsedSeconds
		{
			get
			{
				if (null == this.client)
					return 0;
				return this.client.ElapsedSeconds;
			}
		}
		/// <summary>
		/// Gets the estimated transfer rate (Bytes/Sec) of the upload.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public double TransferRate
		{
			get
			{
				if (null == this.client)
					return 0;
				return this.client.TransferRate;
			}
		}
		#endregion

		#region General
		/// <summary>
		/// Gets the Name of the file.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public string FileName
		{
			get { return _fileName; }
			protected set
			{
				_fileName = value;
				this.OnPropertyChanged("FileName");
			}
		} string _fileName;
		/// <summary>
		/// Gets the Length of the file.
		/// </summary>
		[ScriptableMember]
		[Browsable(false)]
		public long FileLength
		{
			get { return _fileLength; }
			protected set
			{
				_fileLength = value;
				this.OnPropertyChanged("FileLength");
			}
		} long _fileLength;
		/// <summary>
		/// Gets or sets the Tooltip to display when hovered.
		/// </summary>
		[Browsable(false)]
		public string ToolTipText
		{
			get;
			set;
		}
		/// <summary>
		/// Gets if this file is currently selected.
		/// </summary>
		[Browsable(false)]
		public bool IsSelected
		{
			get { return _isSelected; }
			set
			{
				if (_isSelected != value)
				{
					_isSelected = value;
					this.OnPropertyChanged("IsSelected");
				}
			}
		} bool _isSelected;
		#endregion

		#region VisualState
		/// <summary>
		/// Gets or sets the IsRemoveButtonVisible value.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This property is for binding a Remove button's Visibility value from the <see cref="DataTemplate"/>.
		/// </para>
		/// </remarks>
		[Browsable(false)]
		public bool IsRemoveButtonVisible
		{
			get { return this._IsRemoveButtonVisible; }
			set
			{
				if (this._IsRemoveButtonVisible != value)
				{
					this._IsRemoveButtonVisible = value;
					this.OnPropertyChanged("IsRemoveButtonVisible");
				}
			}
		} bool _IsRemoveButtonVisible = false;
		/// <summary>
		/// Gets or sets the IsCancelButtonVisible value.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This property is for binding a Cancel button's Visibility value from the <see cref="DataTemplate"/>.
		/// </para>
		/// </remarks>
		[Browsable(false)]
		public bool IsCancelButtonVisible
		{
			get { return this._isCancelButtonVisible; }
			set
			{
				if (this._isCancelButtonVisible != value)
				{
					this._isCancelButtonVisible = value;
					this.OnPropertyChanged("IsCancelButtonVisible");
				}
			}
		} bool _isCancelButtonVisible = false;
		/// <summary>
		/// Gets or sets the FileNameWidth value.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This property is for binding the width of a cell that displays the file's Name from the <see cref="DataTemplate"/>.
		/// When the owning <see cref="UploadControl"/> is resized, it will attempt to update this property to match the displayed
		/// file name to the file name header by calling <see cref="UpdateFileNameWidth"/>.
		/// </para>
		/// </remarks>
		[Browsable(false)]
		public double FileNameWidth
		{
			get { return this._fileNameWidth; }
			set
			{
				if (this._fileNameWidth != value)
				{
					this._fileNameWidth = value;
					this.OnPropertyChanged("FileNameWidth");
				}
			}
		} double _fileNameWidth;
		/// <summary>
		/// Gets or sets the FileNameColor value.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This property is for binding the foregound color of a cell that displays the file's Name from the <see cref="DataTemplate"/>.
		/// </para>
		/// </remarks>
		[Browsable(false)]
		public Brush FileNameColor
		{
			get { return this._fileNameColor; }
			set
			{
				if (this._fileNameColor != value)
				{
					this._fileNameColor = value;
					this.OnPropertyChanged("FileNameColor");
				}
			}
		} Brush _fileNameColor = new SolidColorBrush(Colors.Black);
		/// <summary>
		/// Gets or sets the FileSizeColor value.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This property is for binding the foregound color of a cell that displays the file's Size from the <see cref="DataTemplate"/>.
		/// </para>
		/// </remarks>
		[Browsable(false)]
		public Brush FileSizeColor
		{
			get { return this._fileSizeColor; }
			set
			{
				if (this._fileSizeColor != value)
				{
					this._fileSizeColor = value;
					this.OnPropertyChanged("FileSizeColor");
				}
			}
		} Brush _fileSizeColor = new SolidColorBrush(Colors.Black);
		/// <summary>
		/// Gets or sets the FileStateColor value.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This property is for binding the foregound color of a cell that displays the file's State from the <see cref="DataTemplate"/>.
		/// </para>
		/// </remarks>
		[Browsable(false)]
		public Brush FileStateColor
		{
			get { return this._fileStateColor; }
			set
			{
				if (this._fileStateColor != value)
				{
					this._fileStateColor = value;
					this.OnPropertyChanged("FileStateColor");
				}
			}
		} Brush _fileStateColor = new SolidColorBrush(Colors.Black);
		#endregion

		#region Configuration
		/// <summary>
		/// Gets or sets the metadata string associated with this file.
		/// </summary>
		[Browsable(false)]
		public string Metadata
		{
			get { return _metadata; }
			set
			{
				if (_metadata != value)
				{
					_metadata = value;
					this.OnPropertyChanged("Metadata");
				}
			}
		} string _metadata = string.Empty;
		/// <summary>
		/// Gets or sets whether or not to automatically close the source stream when the upload completes.
		/// </summary>
		[Browsable(false)]
		public bool AutoCloseStream
		{
			get { return _autoCloseStream; }
			set
			{
				if (_autoCloseStream != value)
				{
					_autoCloseStream = value;
					this.OnPropertyChanged("AutoCloseStream");
				}
			}
		} bool _autoCloseStream = true;
		/// <summary>
		/// Gets or sets whether or not to overwrite existing files.
		/// </summary>
		[Browsable(false)]
		public bool AllowOverwrite
		{
			get { return _allowOverwrite; }
			set
			{
				if (_allowOverwrite != value)
				{
					_allowOverwrite = value;
					this.OnPropertyChanged("AllowOverwrite");
				}
			}
		} bool _allowOverwrite = true;
		#endregion

		#endregion

		#region Methods

		#region State Management
		/// <summary>
		/// Updates the commands state when the <see cref="State"/> changes.
		/// </summary>
		protected virtual void OnCommandStateChanged()
		{
			this.CancelCommand.OnCanExecuteChanged();
			this.RemoveCommand.OnCanExecuteChanged();
			this.ShowDetailCommand.OnCanExecuteChanged();
		}
		/// <summary>
		/// Updates the visual state properties based on the new <see cref="State"/>.
		/// </summary>
		/// <param name="state">The new <see cref="State"/></param>
		protected virtual void UpdateVisualState(FileState state)
		{
			switch (state)
			{
				case FileState.Canceling:
					this.IsRemoveButtonVisible = false;
					this.IsCancelButtonVisible = false;
					this.FileNameColor = GRAY;
					this.FileSizeColor = GRAY;
					this.FileStateColor = GRAY;
					break;

				case FileState.Canceled:
					this.IsRemoveButtonVisible = false;
					this.IsCancelButtonVisible = false;
					this.FileNameColor = GRAY;
					this.FileSizeColor = GRAY;
					this.FileStateColor = GRAY;
					break;

				case FileState.Error:
					this.IsRemoveButtonVisible = false;
					this.IsCancelButtonVisible = false;
					this.FileNameColor = BROWN;
					this.FileSizeColor = BROWN;
					this.FileStateColor = BROWN;
					break;

				case FileState.Finished:
					this.IsRemoveButtonVisible = false;
					this.IsCancelButtonVisible = false;
					this.FileNameColor = GRAY;
					this.FileSizeColor = GRAY;
					this.FileStateColor = GRAY;
					break;

				case FileState.Pending:
					this.IsRemoveButtonVisible = false;
					this.IsCancelButtonVisible = true;
					this.FileNameColor = GRAY;
					this.FileSizeColor = GRAY;
					this.FileStateColor = GRAY;
					break;

				case FileState.Ready:
					this.IsRemoveButtonVisible = true;
					this.IsCancelButtonVisible = false;
					break;

				case FileState.Uploading:
					this.IsRemoveButtonVisible = false;
					this.IsCancelButtonVisible = true;
					this.FileNameColor = GRAY;
					this.FileSizeColor = GRAY;
					this.FileStateColor = GRAY;
					break;
			}
		}
		/// <summary>
		/// Called from the <see cref="UploadControl"/> when resized.
		/// </summary>
		/// <param name="newWidth">The new width of the file name column.</param>
		protected internal virtual void UpdateFileNameWidth(double newWidth)
		{
			this.FileNameWidth = newWidth - INFO_COLUMN_WIDTH;
		}
		#endregion

		#region Initialization
		/// <summary>
		/// This method is called by the <see cref="UploadControlViewModel"/> to initialize this instance.
		/// </summary>
		/// <param name="stream">The stream to be uploaded.</param>
		/// <param name="file">The name of the file the stream represents.</param>
		/// <param name="uploadClientType">The Type of the <see cref="IUploadClient"/> to process the upload request.</param>
		/// <param name="config">The AppSetting Key of your designated File Handler.</param>
		/// <param name="service">The Uri of the remote service to process the upload request.</param>
		protected internal virtual void Initialize(Stream stream, string file, string uploadClientType, string config, string service)
		{
			if (null == stream)
				throw new ArgumentNullException("stream");
			this.uploadStream = stream;

			this.clientType = uploadClientType;
			this.serviceUri = service;
			this.clientConfig = config;

			this.FileLength = stream.Length;
			this.FileName = file;

			this.CancelCommand = new InterlinkCommand(this.CancelAsync, this.CanCancel);
			this.RemoveCommand = new InterlinkCommand(this.Remove, this.CanRemove);
			this.ShowDetailCommand = new InterlinkCommand(this.ShowDetail, this.CanShowDetail);

			this.State = FileState.Ready;
		}
		/// <summary>
		/// This method is called by the <see cref="UploadManager"/> to prepare this model for uploading.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		protected internal virtual bool Prepare()
		{
			if (this.State != FileState.Ready)
			{
				this.State = FileState.Error;
				this.Error = "Cannot prepare file for upload when the file is not in a ready state.";
				return false;
			}

			try
			{
				this.State = FileState.Pending;

				if (null == this.client)
				{
					try
					{
						var tc = UploadControlViewModel.ResolveType(this.clientType);
						if (null == tc) throw new TypeLoadException("Unable to resolve the UploadClient Type [" + this.clientType + "]");
						this.client = (IUploadClient)Activator.CreateInstance(tc);
						this.client.Initialize(this.serviceUri, this.clientConfig, this.AllowOverwrite);
						this.client.UploadCompleted += client_UploadCompleted;
						this.client.UploadProgressChanged += client_UploadProgressChanged;
					}
					catch (FileNotFoundException fnfex)
					{
						var msg = fnfex.ToString();
					}
				}

				if (null != this.GetMetadataCallback)
					this.Metadata = this.GetMetadataCallback(this.FileName);

				return true;
			}
			catch (Exception ex)
			{
				this.State = FileState.Error;
				this.Error = ex.Message;
				return false;
			}
		}
		#endregion

		#region Actions

		#region Upload
		/// <summary>
		/// This method is called by the <see cref="UploadManager"/> to begin the upload processing for this file.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		protected internal virtual void UploadAsync()
		{
			try
			{
				this.State = FileState.Uploading;
				this.client.UploadStreamAsync(this.uploadStream, this.FileName, this.Metadata, this.AutoCloseStream, null);
			}
			catch (Exception ex)
			{
				this.Error = ex.ToString();
				this.State = FileState.Error;
				System.Threading.ThreadPool.QueueUserWorkItem((o) =>
				{
					this.OnUploadCompleted(new UploadFileCompletedEventArgs(string.Empty, o as Exception, false, null));
				}, new Exception(ex.Message));
			}
		}
		#endregion

		#region Cancel
		/// <summary>
		/// Check to ensure the file is in a state that is acceptable for being canceled.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This method is also available for Command binding in a data template.
		/// </para>
		/// </remarks>
		/// <returns>true if the file is in a state sufficient for canceling.</returns>
		protected virtual bool CanCancel()
		{
			return this.State == FileState.Uploading || this.State == FileState.Pending;
		}
		/// <summary>
		/// Attempts to cancel the uploading of this file.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		protected internal virtual void CancelAsync()
		{
			if (this._state == FileState.Uploading)
			{
				try
				{
					if (this._state == FileState.Finished)
						return;
					this.client.CancelAsync();
					this._state = FileState.Canceling;
					this.OnCommandStateChanged();
					this.OnPropertyChanged("State");
					this.OnPropertyChanged("StateString");
					this.UpdateVisualState(this._state);
				}
				catch (Exception ex)
				{
					this.Error = ex.Message;
					this._state = FileState.Error;
					this.OnCommandStateChanged();
					this.OnPropertyChanged("State");
					this.OnPropertyChanged("StateString");
					this.UpdateVisualState(this._state);
				}
			}
			else if (this._state == FileState.Pending)
			{
				this._state = FileState.Canceled;
				this.OnCommandStateChanged();
				this.OnPropertyChanged("State");
				this.OnPropertyChanged("StateString");
				this.UpdateVisualState(this._state);
			}
		}
		#endregion

		#region Remove
		/// <summary>
		/// Check to ensure the file is in a state that is acceptable for being removed.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This method is also available for Command binding in a data template.
		/// </para>
		/// </remarks>
		/// <returns>true if the file is in a state sufficient for being removed.</returns>
		protected virtual bool CanRemove()
		{
			return this.State == FileState.Ready;
		}
		/// <summary>
		/// Marks this file for removal by setting it's state to <see cref="FileState.Removed"/>.
		/// </summary>
		protected virtual void Remove()
		{
			this.State = FileState.Removed;
		}
		#endregion

		#region ShowDetail
		/// <summary>
		/// Check to ensure the file is in a state that is acceptable for showing the file details.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This method is also available for Command binding in a data template.
		/// </para>
		/// </remarks>
		/// <returns>true if the file is in a state sufficient for displaying details.</returns>
		protected virtual bool CanShowDetail()
		{
			return this.State == FileState.Ready ||
				this.State == FileState.Canceled ||
				this.State == FileState.Error ||
				this.State == FileState.Finished;
		}
		/// <summary>
		/// Calls the <see cref="ViewFileDetailCallback"/> delegate allowing the host to show a dialog displaying the details of this file.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This method is also available for Command binding in a data template.
		/// </para>
		/// </remarks>
		protected virtual void ShowDetail()
		{
			if (null != this.ViewFileDetailCallback)
				this.ViewFileDetailCallback(this);
		}
		#endregion

		#endregion

		#endregion

		#region Event Handlers
		void client_UploadProgressChanged(object sender, UploadFileProgressEventArgs e)
		{
			this.BytesUploaded = e.BytesSent;
			this.Percentage = e.ProgressPercentage;
			this.OnUploadProgressChanged(e);
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		void client_UploadCompleted(object sender, UploadFileCompletedEventArgs e)
		{
			if (e.Cancelled)
			{
				this._state = FileState.Canceled;
			}
			else if (null != e.Error)
			{
				this.Error = e.Error.Message;
				this._state = FileState.Error;
			}
			else
			{
				this._state = FileState.Finished;
				this.Results = e.Result;
			}

			this.OnCommandStateChanged();
			this.OnPropertyChanged("State");
			this.OnPropertyChanged("StateString");
			this.UpdateVisualState(this._state);

			try { this.OnUploadCompleted(e); }
			catch { }
		}
		#endregion
	}
	#endregion
}